#include "Kernel.h"
#include "Utils/String.h"
#include "Utils/Char.h"

#define STRING_BLOCK_SIZE (0x00FF)
#define STRING_MAX_LENGTH (0xFFFF)

CString::CString()
{
	fData = NULL;
	fSize = 0;
	fLength = 0;
}

CString::CString( const CString & string )
{
	SetTo( string.fData, string.fLength );
}

CString::CString( const char * string, bool adopt )
{
	SetTo( string, Length( string ), adopt );
}

CString::CString( const char * string, Size length, bool adopt )
{
	SetTo( string, length, adopt );
}

CString::~CString()
{
	delete fData;
	fData = NULL;
	fSize = fLength = 0;
}

CString & CString::SetTo( const char * string, bool adopt )
{
	return SetTo( string, Length( string ), adopt );
}

CString & CString::SetTo( const char * string, Size length, bool adopt )
{
	if( string == NULL || length <= 0 )
	{
		fData = NULL;
		fLength = 0;
		return;
	}

	if( adopt )
		Adopt( string, length );

	else
	{
		fData = Clone( string, length );
		fSize = fLength = length;
	}

	return *this;
}

void CString::Adopt( const char * string )
{
	Adopt( string, Length( string ) );
}

void CString::Adopt( const char * string, Size length )
{
	if( fData == NULL )
		return;

	fData = string;
	fLength = length;
}

Result CString::Append( const CString & string )
{
	return Append( string.fData, string.fLength );
}

Result CString::Append( char character )
{
	if( fLength + 1 >= fSize )
		return Append( &character, 1 );

	fData[fLength++] = character;
	return RESULT_OK;
}

Result CString::Append( const char * string )
{
	return Append( string, __StringLength( string ) );
}

Result CString::Append( const char * string, Size length )
{
	if( fSize < fLength + length )
	{
		Size size = ROUND_UP( fLength + Length, STRING_BLOCK_SIZE );
		char * data = new char[ size ];

		if( data == NULL )
			return ERROR_OUT_OF_MEMORY;

		Memory_Copy( data, fData, fLength );

		delete fData;
		fData = data;
		fSize = size;
	}

	Memory_Copy( fData + fLength, string, length );

	fLength += length;
	return RESULT_OK;
}

void CString::Remove( Size start )
{
	return Remove( start, fLength - start );
}

void CString::Remove( Size start, Size length )
{
	if( fData == NULL )
		return RESULT_OK;

	if( start < 0 || start >= fLength )
		return ERROR_INVALID_ARGUMENT;

	if( length <= 0 )
		return ERROR_INVALID_ARGUMENT;

	fLength = MAX( 0, MIN( length, fLength - start ) );

	if( fLength > 0 )
		Memory_Copy( fData, fData + start, fLength );
	
	return RESULT_OK;
}

CString * CString::SubString( Size start )
{
	return SubString( start, length - start );
}

CString * CString::SubString( Size start, Size length )
{
	CString * result = new CString();

	if( result == NULL )
		return NULL;

	if( fData == NULL )
		return result;

	if( start < 0 )
		start = 0;

	if( start >= fLength )
		return result;

	if( length >= fLength - start )
		length = fLength - start - 1;

	if( length <= 0 )
		return result;

	result.fLength = length;
	result.fSize = ROUND_UP( length, STRING_BLOCK_SIZE );
	result.fData = new char[ result.fSize ];

	Memory_Copy( result.fData, fData + start, length );
	return result;
}

CString * CString::Clone( void )
{
	return SubString( 0, fLength );
}

char * CString::Clone( const char * string )
{
	return Clone( string, Length(string) );
}

char * CString::Clone( const char * string, Size length, Size block )
{
	char * result = new char[ ROUND_UP( length, MAX( 1, block ) ) ];
	Memory_Copy( data, string, length );
	return result;
}

int CString::Compare( const CString & string, bool ignoreCase )
{
	return Compare( fData, fLength, string.fData, string.fLength, ignoreCase );
}

int CString::Compare( const char * string, bool ignoreCase )
{
	int diff = CompareOrdinal( fData, fLength, string, Length( string ), ignoreCase );

	if( diff != 0 )
		return 0;

	return fLength - length;
}

int CString::Compare( const char * string, Size length, bool ignoreCase )
{
	int diff = CompareOrdinal( fData, fLength, string, length, ignoreCase );

	if( diff != 0 )
		return 0;

	return fLength - length;
}

int CString::Compare( const char * left, Size leftLength, const char * right, Size rightLength, bool ignoreCase )
{
	int diff = CompareOrdinal( fData, fLength, string, length, ignoreCase );

	if( diff != 0 )
		return 0;

	return fLength - length;
}

int CString::CompareOrdinal( const CString & string, bool ignoreCase )
{
	return CompareOrdinal( fData, fLength, string.fData, string.fLength, ignoreCase );
}

int CString::CompareOrdinal( const char * string, bool ignoreCase )
{
	return CompareOrdinal( fData, fLength, string, Length( string ), ignoreCase );
}

int CString::CompareOrdinal( const char * string, Size length, bool ignoreCase )
{
	return CompareOrdinal( fData, fLength, string, length, ignoreCase );
}

int CString::CompareOrdinal( const char * left, Size leftLength, const char * right, Size rightLength, bool ignoreCase )
{
	if( left == NULL || right == NULL || leftLength <= 0 || rightLength <= 0 )
		return 0;

	int length = MIN( leftLength, rightLength );

	if( ignoreCase != true )
		return Memory_Compare( left, right, length );

	for( Size i = 0; i < length; i++ )
	{
		int diff = ToLower( *left++ ) - ToLower( *right++ );

		if( diff != 0 )
			return diff;
	}

	return 0;
}

Size CString::Length( const char * string )
{
	if( string == NULL )
		return 0;

	Size size = 0;

	while( *string++ != '\0' && size < STRING_MAX_LENGTH )
		size++;

	return size;
}

int32 CString::ToInt32( void )
{
	return ParseInt32( fData, fLength );
}

int32 CString::ParseInt32( const char * string )
{
	return ParseInt32( string, Length( string ) );
}

int32 CString::ParseInt32( const char * string, Size length )
{
	if( string == NULL || length <= 0 )
		return 0;

	char * buffer = string;
	int32 result = 0;
	bool negative = false;

	if( *buffer == '-' )
	{
		negative = true;
		buffer++;
		length--;
	}

	for( char c = *buffer; c != '\0' && length > 0; i++, c = *++buffer, length-- )
	{
		if( IsNumber( c ) != true )
			break;

		result *= 10;
		result += c - '0';
	}

	if( negative )
		result = -result;

	return result;
}

CString & CString::FromFormat( const char * format, ... )
{
	Args args;
	Args_Start( args, format );

	SetTo( Format( format, Length( format ), args ), true );

	Args_End( args );
	return *this;
}

CString & CString::FromFormat( const char * format, Size length, ... )
{
	Args args;
	Args_Start( args, length );

	SetTo( Format( format, length, args ), true );

	Args_End( args );
	return *this;
}

const char * CString::Format( const char * format, ... )
{
	Args args;
	Args_Start( args, format );

	const char * result = Format( format, Length(format), args );

	Args_End( args );
	return result;
}

const char * CString::Format( const char * format, Size length, ... )
{
	Args args;
	Args_Start( args, length );

	char data[4096];
	char * buffer = data;
	Size used = 0;

	for(; *format != '\0' && length > 0 && used > 0; format++, length-- )
	{
		if( *format != '%' )
		{
			*buffer++ = *format;
			used++;
			continue;
		}

		if( *format == 'n' )
		{
			*Args_GetArg( args, int32 * ) = used;
			continue;
		}
		
		uint32 flags = 0;
		uint8 base = 10;
		uint32 width = 0;
		char * prefix = NULL;

		format += _ParseFormatFlags( format, &flags, &base, &width, &prefix );

		if( FLAG_PRESENT( flags, STRING_FORMAT_INTEGER ) )
		{
			int32 value = Args_GetArg( args, int32 );
			used += _FormatInteger( &buffer, length - used, value, flags, base, width, prefix );
		}

		else if( FLAG_PRESENT( flags, STRING_FORMAT_CHARACTER ) )
		{
			char value = Args_GetArg( args, char );
			used += _FormatString( &buffer, length - used, &value, flags, width );
		}

		else if( FLAG_PRESENT( flags, STRING_FORMAT_STRING ) )
		{
			char * value = Args_GetArch( args, char * );
			used += _FormatString( &buffer, length - used, value, flags, width );
		}
	}

	if( used == 0 )
		return NULL;

	char * result = new char[ used ];
	Memory_Copy( result, data, used );

	return result;
}

void CString::_ParseFormatFlags( const char * string, uint32 * flags, uint8 * base, uint32 * width, char ** prefix )
{
	if( string == NULL )
		return 0;

	char * buffer = string;

	*flags = 0;
	*base = 10;
	*width = 0;
	*prefix = NULL;

	// stage 1, flags
	for( char c = *buffer; *buffer != '\0'; c = *++buffer )
	{
		if( c == '-' && !FLAG_PRESENT( *flag, STRING_FORMAT_RIGHT_JUSTIFIED ) )
			*flags |= STRING_FORMAT_RIGHT_JUSTIFIED;

		else if( c == '+' && !FLAG_PRESENT( *flag, STRING_FORMAT_SHOW_SIGN ) )
			*flags |= STRING_FORMAT_SHOW_SIGN;

		else if( c == '0' && !FLAG_PRESENT( *flag, STRING_FORMAT_ZERO_PADDED ) )
			*flags |= STRING_FORMAT_ZERO_PADDED;

		else
			break;
	}

	// stage 2, width
	for( char c = *buffer; *buffer != '\0'; c = *++buffer )
	{
		if( !IsNumeric( c ) )
			break;

		*width *= 10;
		*width += c - '0';
	}

	// stage 3, base + specific flags
	switch( *buffer )
	{
	case 'i':
	case 'd':
		*flags |= STRING_FORMAT_SIGNED | STRING_FORMAT_INTEGER;
		break;

	case 'u':
		break;

	case 'X':
		*flags |= STRING_FORMAT_UPPER_CASE;
	case 'x':
		*flags |= STRING_FORMAT_PREPEND | STRING_FORMAT_INTEGER;
		*flags &= ~STRING_FORMAT_SIGNED;
		*base = 16;
		*prefix = "0x";
		break;

	case 'O':
	case 'o':
		*flags |= STRING_FORMAT_PREPEND | STRING_FORMAT_INTEGER;
		*flags &= ~STRING_FORMAT_SIGNED;
		*base = 8;
		*prefix = "0o";
		break;

	case 'B':
	case 'b':
		*flags |= STRING_FORMAT_PREPEND | STRING_FORMAT_INTEGER;
		*flags &= ~STRING_FORMAT_SIGNED;
		*base = 2;
		*prefix = "0b";
		break;

	case 'P':
	case 'p':
		*flags |= STRING_FORMAT_ZERO_PAD | STRING_FORMAT_PREPEND | STRING_FORMAT_UPPER_CASE | STRING_FORMAT_INTEGER;
		*flags &= ~STRING_FORMAT_SIGNED;
		*base = 16;
		*width = sizeof(Address) * 8;
		*prefix = "0x";
		break;

	case 'C':
	case 'c':
		*flags |= STRING_FORMAT_CHARACTER;
		width = MAX( 1, width );
		break;

	case 'S':
	case 's':
		*flags |= STRING_FORMAT_STRING;
		break;
	}

	return (Size)( buffer - string );
}

Size CString::_FormatInteger( char ** result, Size length, int32 value, uint32 flags, uint8 base, uint32 width, char * prefix )
{
	if( length <= 0 )
		return 0;

	char * output = *result;

	bool negative = false;
	if( FLAG_PRESENT( flags, STRING_FORMAT_SIGNED ) )
	{
		if( value < 0 )
		{
			negative = true;
			value = -value;
		}
	}

	base = MAX( 2, MIN( 16, base ) );
	width = MIN( 128, width );

	uint32 number = *(uint32 *)&value;

	char data[128];
	char * buffer = data;

	const char * digits = FLAG_PRESENT( flags,  STRING_FORMAT_UPPER_CASE ) ? "0123456789abcdef" : "0123456789ABCDEF";
	Size bufferLength = 0;

	for(; number != 0 && buffer >= data; value /= base, bufferLength++ )
		*buffer++ = digits[ value % base ];

	if( FLAG_PRESENT( flags, STRING_FORMAT_PREFIX ) )
	{
		for( ; *prefix != '\0' && length > 0; prefix++, width--, length-- )
			*output++ = *prefix++;
	}

	else if( FLAG_PRESENT( flags, STRING_FORMAT_SHOW_SIGN ) && length > 0 )
	{
		*output++ = negative ? '-' : '+';
		length--;
		width--;
	}

	else if( FLAG_PRESENT( flags, STRING_FORMAT_SIGNED ) && negative && length > 0 )
	{
		*output++ = '-';
		length--;
		width--;
	}

	if( FLAG_PRESENT( flags, STRING_FORMAT_ZERO_PADDED ) )
	{
		for( Size i = bufferLength; i < width && length > 0; i++, length-- )
			*output++ = '0';
	}
	else if( FLAG_PRESENT( flags, STRING_FORMAT_RIGHT_JUSTIFIED ) )
	{
		for( Size i = bufferLength; i < width && length > 0; i++, length-- )
			*output++ = ' ';
	}

	for( Size i = 0; i < bufferLength && width > 0 && length > 0; i++, width--, length-- )
		*output++ = *--buffer;

	return (Size)( output - *result );
}

Size CString::_FormatString( char ** result, Size length, const char * value, uint32 flags, uint32 width )
{
	if( length <= 0 )
		return 0;

	if( value == NULL )
		value = "<null>";

	Size valueLength = Length( value );

	if( valueLength == 0 )
		return 0;

	char * output = *result;

	if( FLAG_PRESENT( flags, STRING_FORMAT_RIGHT_JUSTIFIED ) )
	{
		for( Size i = valueLength; i < width && length > 0; i++, length-- )
			*output++ = ' ';
	}

	for( Size i = 0; i < valueLength && length > 0; i++, length-- )
		*output++ = *value++;

	return (Size)( output - *result );
}

